package edu.northwestern.cbits.purple_robot_manager.probes.builtin;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Map;
import org.apache.commons.net.ntp.NTPUDPClient;
import org.apache.commons.net.ntp.TimeInfo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.os.StatFs;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import edu.northwestern.cbits.purple_robot_manager.BootUpReceiver;
import edu.northwestern.cbits.purple_robot_manager.ManagerService;
import edu.northwestern.cbits.purple_robot_manager.R;
import edu.northwestern.cbits.purple_robot_manager.ShutdownReceiver;
import edu.northwestern.cbits.purple_robot_manager.activities.settings.FlexibleListPreference;
import edu.northwestern.cbits.purple_robot_manager.config.JSONConfigFile;
import edu.northwestern.cbits.purple_robot_manager.config.SchemeConfigFile;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.logging.SanityManager;
import edu.northwestern.cbits.purple_robot_manager.plugins.HttpUploadPlugin;
import edu.northwestern.cbits.purple_robot_manager.plugins.OutputPlugin;
import edu.northwestern.cbits.purple_robot_manager.plugins.OutputPluginManager;
import edu.northwestern.cbits.purple_robot_manager.plugins.StreamingJacksonUploadPlugin;
import edu.northwestern.cbits.purple_robot_manager.probes.Probe;
import edu.northwestern.cbits.purple_robot_manager.triggers.TriggerManager;
// import org.apache.commons.net.ntp.NTPUDPClient;
// import org.apache.commons.net.ntp.TimeInfo;
public class RobotHealthProbe extends Probe
{
private static final String PENDING_COUNT = "PENDING_COUNT";
private static final String PENDING_SIZE = "PENDING_SIZE";
// private static final String ARCHIVE_COUNT = "ARCHIVE_COUNT";
// private static final String ARCHIVE_SIZE = "ARCHIVE_SIZE";
private static final String THROUGHPUT = "THROUGHPUT";
private static final String CLEAR_TIME = "CLEAR_TIME";
protected static final String APP_VERSION_NAME = "APP_VERSION_NAME";
protected static final String APP_VERSION_CODE = "APP_VERSION_CODE";
protected static final String ACTIVE_RUNTIME = "ACTIVE_RUNTIME";
private long _lastCheck = 0;
private boolean _checking = false;
private static final long NTP_CHECK_DURATION = 300000;
private static final String NTP_HOST = "0.north-america.pool.ntp.org";
protected static final String TIME_OFFSET_MS = "TIME_OFFSET_MS";
protected static final String CPU_USAGE = "CPU_USAGE";
protected static final String ROOT_FREE = "ROOT_FREE";
protected static final String ROOT_TOTAL = "ROOT_TOTAL";
protected static final String EXTERNAL_FREE = "EXTERNAL_FREE";
protected static final String EXTERNAL_TOTAL = "EXTERNAL_TOTAL";
protected static final String SCHEME_CONFIG = "SCHEME_CONFIG";
protected static final String JSON_CONFIG = "JSON_CONFIG";
protected static final String LAST_BOOT = "LAST_BOOT";
protected static final String LAST_HALT = "LAST_HALT";
private static final boolean DEFAULT_ENABLED = true;
private static final boolean DEFAULT_SCHEME = true;
private static final boolean DEFAULT_JSON = true;
private static final String INCLUDE_SCHEME_CONFIG = "config_probe_robot_scheme_config";
private static final String ENABLED = "config_probe_robot_enabled";
private static final String FREQUENCY = "config_probe_robot_frequency";
private static final String INCLUDE_JSON_CONFIG = "config_probe_robot_json_config";
public static String NAME = "edu.northwestern.cbits.purple_robot_manager.probes.builtin.RobotHealthProbe";
private long _lastOffset = 0;
private long _lastTimeCheck = 0;
@Override
public String getPreferenceKey() {
return "built_in_robot_health";
}
@Override
public String name(Context context)
{
return RobotHealthProbe.NAME;
}
@Override
public String title(Context context)
{
return context.getString(R.string.title_robot_probe);
}
@Override
public String probeCategory(Context context)
{
return context.getResources().getString(R.string.probe_device_info_category);
}
@Override
public void enable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(RobotHealthProbe.ENABLED, true);
e.commit();
}
@Override
public void disable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(RobotHealthProbe.ENABLED, false);
e.commit();
}
// Source:
// http://stackoverflow.com/questions/3118234/how-to-get-memory-usage-and-cpu-usage-in-android
private float readUsage(Context context)
{
try
{
RandomAccessFile reader = new RandomAccessFile("/proc/stat", "r");
String load = reader.readLine();
String[] toks = load.split(" ");
long idle1 = Long.parseLong(toks[5]);
long cpu1 = Long.parseLong(toks[2]) + Long.parseLong(toks[3]) + Long.parseLong(toks[4]) + Long.parseLong(toks[6]) + Long.parseLong(toks[7]) + Long.parseLong(toks[8]);
try
{
Thread.sleep(360);
}
catch (Exception e)
{
}
reader.seek(0);
load = reader.readLine();
reader.close();
toks = load.split(" ");
long idle2 = Long.parseLong(toks[5]);
long cpu2 = Long.parseLong(toks[2]) + Long.parseLong(toks[3]) + Long.parseLong(toks[4]) + Long.parseLong(toks[6]) + Long.parseLong(toks[7]) + Long.parseLong(toks[8]);
return (float) (cpu2 - cpu1) / ((cpu2 + idle2) - (cpu1 + idle1));
}
catch (IOException e)
{
LogManager.getInstance(context).logException(e);
}
return 0;
}
@Override
public boolean isEnabled(final Context context)
{
final SharedPreferences prefs = Probe.getPreferences(context);
final long now = System.currentTimeMillis();
if (super.isEnabled(context))
{
if (prefs.getBoolean(RobotHealthProbe.ENABLED, RobotHealthProbe.DEFAULT_ENABLED))
{
synchronized (this)
{
long freq = Long.parseLong(prefs.getString(RobotHealthProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY));
if (now - this._lastCheck > freq)
{
OutputPlugin plugin = OutputPluginManager.sharedInstance.pluginForClass(context, HttpUploadPlugin.class);
if (plugin != null && plugin instanceof HttpUploadPlugin)
{
final HttpUploadPlugin httpPlugin = (HttpUploadPlugin) plugin;
final RobotHealthProbe me = this;
Runnable r = new Runnable()
{
@Override
@SuppressWarnings("deprecation")
public void run()
{
if (me._checking)
return;
if (Looper.myLooper() == null)
Looper.prepare();
me._checking = true;
int pendingCount = 0;
// TODO: Pull string values into
// constants...
if (prefs.getBoolean("config_enable_data_server", false))
{
OutputPlugin plugin = OutputPluginManager.sharedInstance.pluginForClass(context, HttpUploadPlugin.class);
if (plugin instanceof HttpUploadPlugin)
{
HttpUploadPlugin http = (HttpUploadPlugin) plugin;
pendingCount += http.pendingFilesCount();
}
}
else
{
OutputPlugin plugin = OutputPluginManager.sharedInstance.pluginForClass(context, StreamingJacksonUploadPlugin.class);
if (plugin instanceof StreamingJacksonUploadPlugin)
{
StreamingJacksonUploadPlugin http = (StreamingJacksonUploadPlugin) plugin;
pendingCount += http.pendingFilesCount();
}
}
long pendingSize = 0;
if (prefs.getBoolean("config_enable_data_server", false))
{
OutputPlugin plugin = OutputPluginManager.sharedInstance.pluginForClass(context, HttpUploadPlugin.class);
if (plugin instanceof HttpUploadPlugin)
{
HttpUploadPlugin http = (HttpUploadPlugin) plugin;
pendingSize += http.pendingFilesSize();
}
}
else
{
OutputPlugin plugin = OutputPluginManager.sharedInstance.pluginForClass(context, StreamingJacksonUploadPlugin.class);
if (plugin instanceof StreamingJacksonUploadPlugin)
{
StreamingJacksonUploadPlugin http = (StreamingJacksonUploadPlugin) plugin;
pendingSize += http.pendingFilesSize();
}
}
Bundle bundle = new Bundle();
bundle.putString("PROBE", me.name(context));
bundle.putLong("TIMESTAMP", System.currentTimeMillis() / 1000);
bundle.putBoolean("PRIORITY", true);
bundle.putInt(RobotHealthProbe.PENDING_COUNT, pendingCount);
bundle.putLong(RobotHealthProbe.PENDING_SIZE, pendingSize);
double throughput = httpPlugin.getRecentThroughput();
bundle.putDouble(RobotHealthProbe.THROUGHPUT, throughput);
long cleartime = -1;
if (throughput > 0.0)
cleartime = pendingSize / ((long) throughput);
bundle.putLong(RobotHealthProbe.CLEAR_TIME, cleartime);
// Version checks
try
{
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
bundle.putString(RobotHealthProbe.APP_VERSION_NAME, info.versionName);
bundle.putInt(RobotHealthProbe.APP_VERSION_CODE, info.versionCode);
}
catch (NameNotFoundException | RuntimeException e)
{
LogManager.getInstance(context).logException(e);
}
// NTP checks
if ((now - me._lastTimeCheck) > NTP_CHECK_DURATION)
{
try
{
NTPUDPClient client = new NTPUDPClient();
client.setDefaultTimeout(10000);
TimeInfo info = client.getTime(InetAddress.getByName(NTP_HOST));
if (info != null)
{
info.computeDetails();
if (info.getOffset() != null)
me._lastOffset = info.getOffset();
}
}
catch (Exception e)
{
LogManager.getInstance(context).logException(e);
}
finally
{
me._lastTimeCheck = now;
}
}
bundle.putLong(RobotHealthProbe.TIME_OFFSET_MS, me._lastOffset);
bundle.putLong(RobotHealthProbe.ACTIVE_RUNTIME, System.currentTimeMillis() - ManagerService.startTimestamp);
bundle.putFloat(RobotHealthProbe.CPU_USAGE, me.readUsage(context));
try
{
StatFs root = new StatFs(Environment.getRootDirectory().getAbsolutePath());
int rootFree = root.getAvailableBlocks() * root.getBlockSize();
int rootTotal = root.getBlockCount() * root.getBlockSize();
bundle.putInt(RobotHealthProbe.ROOT_FREE, rootFree);
bundle.putInt(RobotHealthProbe.ROOT_TOTAL, rootTotal);
}
catch (IllegalArgumentException e)
{
LogManager.getInstance(context).logException(e);
}
try
{
StatFs external = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath());
int externalFree = external.getAvailableBlocks() * external.getBlockSize();
int externalTotal = external.getBlockCount() * external.getBlockSize();
bundle.putInt(RobotHealthProbe.EXTERNAL_FREE, externalFree);
bundle.putInt(RobotHealthProbe.EXTERNAL_TOTAL, externalTotal);
}
catch (IllegalArgumentException e)
{
LogManager.getInstance(context).logException(e);
}
if (prefs.getBoolean(RobotHealthProbe.INCLUDE_SCHEME_CONFIG, RobotHealthProbe.DEFAULT_SCHEME))
{
SchemeConfigFile file = new SchemeConfigFile(context);
bundle.putString(RobotHealthProbe.SCHEME_CONFIG, file.toString());
}
if (prefs.getBoolean(RobotHealthProbe.INCLUDE_JSON_CONFIG, RobotHealthProbe.DEFAULT_JSON))
{
JSONConfigFile file = new JSONConfigFile(context);
bundle.putString(RobotHealthProbe.JSON_CONFIG, file.toString());
}
bundle.putLong(RobotHealthProbe.LAST_BOOT, prefs.getLong(BootUpReceiver.BOOT_KEY, 0));
bundle.putLong(RobotHealthProbe.LAST_HALT, prefs.getLong(ShutdownReceiver.SHUTDOWN_KEY, 0));
ArrayList<String> errors = new ArrayList<>();
for (String check : SanityManager.getInstance(context).errors().values())
errors.add(check);
bundle.putStringArrayList("CHECK_ERRORS", errors);
ArrayList<String> warnings = new ArrayList<>();
for (String check : SanityManager.getInstance(context).warnings().values())
warnings.add(check);
bundle.putStringArrayList("CHECK_WARNINGS", warnings);
bundle.putParcelableArrayList("TRIGGERS", TriggerManager.getInstance(context).allTriggersBundles(context));
long later = System.currentTimeMillis();
bundle.putLong("MEASURE_TIME", later - now);
me.transmitData(context, bundle);
me._checking = false;
}
};
Thread t = new Thread(r);
t.start();
}
this._lastCheck = now;
}
}
return true;
}
}
return false;
}
@Override
public Bundle formattedBundle(Context context, Bundle bundle)
{
Bundle formatted = super.formattedBundle(context, bundle);
formatted.putLong(context.getString(R.string.robot_runtime_label), bundle.getLong(RobotHealthProbe.ACTIVE_RUNTIME, 0));
formatted.putFloat(context.getString(R.string.robot_cpu_load_label), bundle.getFloat(RobotHealthProbe.CPU_USAGE, 0));
formatted.putLong(context.getString(R.string.robot_time_offset_label), bundle.getLong(RobotHealthProbe.TIME_OFFSET_MS, 0));
formatted.putInt(context.getString(R.string.robot_pending_count_label), (int) bundle.getDouble(RobotHealthProbe.PENDING_COUNT, 0));
formatted.putLong(context.getString(R.string.robot_pending_size_label), bundle.getLong(RobotHealthProbe.PENDING_SIZE, 0));
formatted.putLong(context.getString(R.string.robot_clear_time_label), bundle.getLong(RobotHealthProbe.CLEAR_TIME, 0));
formatted.putString(context.getString(R.string.robot_version_label), bundle.getString(RobotHealthProbe.APP_VERSION_NAME));
return formatted;
}
@Override
public String summarizeValue(Context context, Bundle bundle)
{
int count = (int) bundle.getDouble(RobotHealthProbe.PENDING_COUNT);
double size = 0.0 + (int) bundle.getDouble(RobotHealthProbe.PENDING_SIZE);
long clear = (int) bundle.getDouble(RobotHealthProbe.CLEAR_TIME);
if (clear < 0)
clear = 0;
size = size / (1024 * 1024);
double cpu = bundle.getDouble(RobotHealthProbe.CPU_USAGE);
return String.format(context.getResources().getString(R.string.summary_robot_probe), cpu, count, size, clear);
}
@Override
public Map<String, Object> configuration(Context context)
{
Map<String, Object> map = super.configuration(context);
SharedPreferences prefs = Probe.getPreferences(context);
long freq = Long.parseLong(prefs.getString(RobotHealthProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY));
map.put(Probe.PROBE_FREQUENCY, freq);
map.put(RobotHealthProbe.INCLUDE_SCHEME_CONFIG, prefs.getBoolean(RobotHealthProbe.INCLUDE_SCHEME_CONFIG, RobotHealthProbe.DEFAULT_SCHEME));
map.put(RobotHealthProbe.INCLUDE_JSON_CONFIG, prefs.getBoolean(RobotHealthProbe.INCLUDE_JSON_CONFIG, RobotHealthProbe.DEFAULT_JSON));
return map;
}
@Override
public void updateFromMap(Context context, Map<String, Object> params)
{
super.updateFromMap(context, params);
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
if (params.containsKey(Probe.PROBE_FREQUENCY))
{
Object frequency = params.get(Probe.PROBE_FREQUENCY);
if (frequency instanceof Double)
{
frequency = ((Double) frequency).longValue();
}
if (frequency instanceof Long)
e.putString(RobotHealthProbe.FREQUENCY, frequency.toString());
}
if (params.containsKey(RobotHealthProbe.INCLUDE_SCHEME_CONFIG))
{
Object include = params.get(RobotHealthProbe.INCLUDE_SCHEME_CONFIG);
if (include instanceof Boolean)
e.putBoolean(RobotHealthProbe.INCLUDE_SCHEME_CONFIG, (Boolean) include);
}
if (params.containsKey(RobotHealthProbe.INCLUDE_JSON_CONFIG))
{
Object include = params.get(RobotHealthProbe.INCLUDE_JSON_CONFIG);
if (include instanceof Boolean)
e.putBoolean(RobotHealthProbe.INCLUDE_JSON_CONFIG, (Boolean) include);
}
e.commit();
}
@Override
public String summary(Context context)
{
return context.getString(R.string.summary_robot_probe_desc);
}
@Override
@SuppressWarnings("deprecation")
public PreferenceScreen preferenceScreen(Context context, PreferenceManager manager)
{
PreferenceScreen screen = super.preferenceScreen(context, manager);
screen.setTitle(this.title(context));
screen.setSummary(R.string.summary_robot_probe_desc);
CheckBoxPreference enabled = new CheckBoxPreference(context);
enabled.setTitle(R.string.title_enable_probe);
enabled.setKey(RobotHealthProbe.ENABLED);
enabled.setDefaultValue(RobotHealthProbe.DEFAULT_ENABLED);
screen.addPreference(enabled);
FlexibleListPreference duration = new FlexibleListPreference(context);
duration.setKey(RobotHealthProbe.FREQUENCY);
duration.setEntryValues(R.array.probe_low_frequency_values);
duration.setEntries(R.array.probe_low_frequency_labels);
duration.setTitle(R.string.probe_frequency_label);
duration.setDefaultValue(Probe.DEFAULT_FREQUENCY);
screen.addPreference(duration);
CheckBoxPreference scheme = new CheckBoxPreference(context);
scheme.setTitle(R.string.title_enable_scheme_config);
scheme.setKey(RobotHealthProbe.INCLUDE_SCHEME_CONFIG);
scheme.setDefaultValue(RobotHealthProbe.DEFAULT_SCHEME);
screen.addPreference(scheme);
CheckBoxPreference json = new CheckBoxPreference(context);
json.setTitle(R.string.title_enable_json_config);
json.setKey(RobotHealthProbe.INCLUDE_JSON_CONFIG);
json.setDefaultValue(RobotHealthProbe.DEFAULT_JSON);
screen.addPreference(json);
return screen;
}
@Override
public JSONObject fetchSettings(Context context)
{
JSONObject settings = super.fetchSettings(context);
try
{
JSONArray values = new JSONArray();
values.put(true);
values.put(false);
JSONObject enabled = new JSONObject();
enabled.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_BOOLEAN);
enabled.put(Probe.PROBE_VALUES, values);
settings.put(RobotHealthProbe.INCLUDE_JSON_CONFIG, enabled);
settings.put(RobotHealthProbe.INCLUDE_SCHEME_CONFIG, enabled);
JSONObject frequency = new JSONObject();
frequency.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG);
values = new JSONArray();
String[] options = context.getResources().getStringArray(R.array.probe_low_frequency_values);
for (String option : options)
{
values.put(Long.parseLong(option));
}
frequency.put(Probe.PROBE_VALUES, values);
settings.put(Probe.PROBE_FREQUENCY, frequency);
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
return settings;
}
public String assetPath(Context context)
{
return "robot-health-probe.html";
}
}